package com.mostka.rpc.rebind;

import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import com.google.gson.Gson;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.dev.util.Util;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.mostka.rpc.client.HasJsSocket;
import com.mostka.rpc.client.WsRpc;
import com.mostka.rpc.client.WsRpcServer;
import com.mostka.rpc.server.HasMethodArgs;
import com.mostka.rpc.server.Socket;
import com.mostka.rpc.shared.HasHash;
import com.mostka.rpc.util.DynamicJavaFileObject;
import com.mostka.ws.client.JsWebSocket;

public class WsRpcServerGenerator extends Generator{

	@Override
	public String generate(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException {
		JClassType classType;
		try {
			classType = context.getTypeOracle().getType(typeName);
			/*String serverName;
			try {
				serverName = context.getPropertyOracle().getConfigurationProperty("serverPath").getValues().get(0);
			} catch (BadPropertyValueException e1) {
				e1.printStackTrace();
				throw new UnableToCompleteException();
			}
			String servicePath = "";
			WsRpcRelocatePath anotation = classType.getAnnotation(WsRpcRelocatePath.class);
			if (anotation!=null)
				servicePath = anotation.value().replaceAll("\\.", "/");
*/
			
			String hashName = Util.computeStrongName((classType.getPackage().getName()+"."+classType.getSimpleSourceName()).getBytes());
			
			try {
				SourceWriter src1 = generateSerializePolicy(classType, context, logger, hashName);
				src1.commit(logger);
				src1 = generateClientServer(classType, context, logger, hashName);
				src1.commit(logger);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			
			SourceWriter src = generateClientMethods( classType, context, logger, hashName);

			if (src == null)return typeName + "__AsyncWsRpcGenerated";
			src.commit(logger);			
			return typeName + "__AsyncWsRpcGenerated";
		} catch (NotFoundException e) {
			e.printStackTrace();
			throw new UnableToCompleteException();
		}
	}
	private SourceWriter generateClientServer(JClassType classType, GeneratorContext context, TreeLogger logger, String hashName) throws UnableToCompleteException, UnsupportedEncodingException {
		JClassType clientType = getClientClass(classType);
		
		String packageName = clientType.getPackage().getName();
		String simpleName = clientType.getSimpleSourceName() + "__ClientServerWsRpcGenerated";

		DynamicJavaFileObject composer = new DynamicJavaFileObject(packageName, simpleName);
		
		composer.addImport(Gson.class.getCanonicalName());
		composer.addImport(Socket.class.getCanonicalName());
		composer.setSuperclass(clientType.getParameterizedQualifiedSourceName());
		
		SourceWriter writer = composer.getSourceWriter();
		
		writer.indent();
		writer.println("private static String HASH_CLASS_NAME = \""+hashName+"\";");
		writer.println("private static final String separator = \"_---_\";");
		writer.println();
		writer.println("private Socket socket;");
		writer.println("private int id;");
		writer.println("private Gson gson = new Gson();");
		writer.println();
		writer.println("public "+simpleName+"(Socket socket, Integer id) {");
		writer.indent();
		writer.println("this.socket = socket;");
		writer.println("this.id = id;");
		writer.outdent();
		writer.println("}");
		writer.println();
		writer.println("@Override public int getClientId() { return id;	};");
		JMethod[] methods = clientType.getMethods();
		int ofset=0;
		for (int methodPos = 0; methodPos < methods.length; methodPos++) {
			JMethod method = methods[methodPos];
			if (method.getName().equals("generateSelf")){
				ofset=1;
				continue;
			}
			JParameter[] parameters = method.getParameters();

			writer.println();
			writer.print("@Override public void "+method.getName()+"(");
			for (int i = 0; i < parameters.length; i++) {
				JParameter parameter = parameters[i];
				writer.print(parameter.getType().getParameterizedQualifiedSourceName()+" "+parameter.getName());	
				if (i < parameters.length-1)
					writer.print(", ");
			}
			writer.println("){");
			writer.indent();
			writer.println("String message_fsdffter = \"\";");
			writer.println("message_fsdffter += HASH_CLASS_NAME + separator;");
			writer.println("message_fsdffter += \""+(methodPos-ofset)+"\" + separator;");
			for (int i = 0; i < parameters.length; i++) {
                String separator = "";
                if (i<parameters.length-1){
                    separator = "+separator";
                }
				JParameter parameter = parameters[i];
				writer.println("message_fsdffter += gson.toJson("+parameter.getName()+")"+separator+";");
			}
			writer.println("socket.send(message_fsdffter);");
			writer.outdent();
			writer.println("}");
		}
		return writer;
	}
	private SourceWriter getSourceClientWriter(JClassType classType, GeneratorContext context, TreeLogger logger) {
		String packageName = classType.getPackage().getName();
		String simpleName = classType.getSimpleSourceName() + "__AsyncWsRpcGenerated";
		
		ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, simpleName);
		
		composer.addImplementedInterface(packageName+"."+classType.getSimpleSourceName()+"Async");
		composer.addImplementedInterface(HasJsSocket.class.getCanonicalName());
		composer.addImplementedInterface(HasHash.class.getCanonicalName());

		composer.addImport(JavaScriptObject.class.getCanonicalName());
		composer.addImport(JSONArray.class.getCanonicalName());
		composer.addImport(JSONObject.class.getCanonicalName());
		composer.addImport(JSONString.class.getCanonicalName());
		composer.addImport(AsyncCallback.class.getCanonicalName());
		composer.addImport(JsWebSocket.class.getCanonicalName());
		composer.addImport(HasJsSocket.class.getCanonicalName());
		composer.addImport(WsRpc.class.getCanonicalName());
		composer.addImport(GWT.class.getCanonicalName());
		composer.addImport(Window.class.getCanonicalName());

		PrintWriter printWriter = context.tryCreate(logger, packageName, simpleName);
		if (printWriter == null) {
			return null;
		}
		return composer.createSourceWriter(context, printWriter);
	}
	
	private SourceWriter generateSerializePolicy(JClassType classType, GeneratorContext context, TreeLogger logger, String hashName) throws UnableToCompleteException, UnsupportedEncodingException {

		String packageName = classType.getPackage().getName();
		String simpleName = classType.getSimpleSourceName() + "__AsyncArgsWsRpcGenerated";
		
		DynamicJavaFileObject composer = new DynamicJavaFileObject(packageName, simpleName);
		
		composer.addImplementedInterface(HasMethodArgs.class.getCanonicalName());
		
		SourceWriter writer = composer.getSourceWriter();
		
		writer.indent();
		writer.println("private static String HASH_CLASS_NAME = \""+hashName+"\";");
		writer.println();
		writer.println("public String getHashName(){return HASH_CLASS_NAME;}");
		writer.println();
			
		writer.println("public Class<?>[] getMethodArgs(int methodPos){");
		writer.indent();
		writer.println("switch(methodPos){");
		
		JMethod[] methods = classType.getMethods();
		
		for (int methodPos = 0; methodPos < methods.length; methodPos++) {
			JMethod method = methods[methodPos];
			JParameter[] parameters = method.getParameters();
			writer.println("case "+methodPos+":return new Class<?>[]{");
			writer.indent();
			
			writer.println(String.class.getCanonicalName()+ ".class,");	
			writer.println(getClientClass(classType).getParameterizedQualifiedSourceName()+ ".class," );	

			for (int i = 2; i < parameters.length; i++) {
				JParameter parameter = parameters[i];
				writer.println(parameter.getType().getParameterizedQualifiedSourceName()+ ".class" + (i < parameters.length-1?",":"") );	

			}
			writer.outdent();
			writer.println("};");
		}
		writer.println("};");
		writer.println("return null;");
		writer.outdent();
		writer.println("};");
		
		writer.println("public String getMethodName(int methodPos){");
		writer.indent();
		writer.println("switch(methodPos){");
		
		for (int methodPos = 0; methodPos < methods.length; methodPos++) {
			JMethod method = methods[methodPos];
			writer.println("case "+methodPos+":return \""+method.getName()+"\";");
			
		}
		writer.println("};");
		writer.println("return null;");
		
		
		writer.outdent();
		writer.println("};");
		
	    return writer;
	}
	
	
	private JClassType getClientClass(JClassType classType){
		for (JClassType g:classType.getImplementedInterfaces()){
			if (g.getQualifiedSourceName().equals(WsRpcServer.class.getCanonicalName())){
				JParameterizedType jj = (JParameterizedType)g;
				JClassType[] args = jj.getTypeArgs();
				if (args.length!=1){
					System.out.println(WsRpcServer.class.getCanonicalName()+" can have only one generics argument");
					return null;
				}
				return args[0];
			}
		}

		
		return null;
	}

	private SourceWriter generateClientMethods(JClassType classType, GeneratorContext context, TreeLogger logger, String hashName) throws UnableToCompleteException {
		JMethod[] methods = classType.getMethods();


			//serverName = context.getPropertyOracle().getConfigurationProperty("serverPath").getValues().get(0);
			//serverName+= "/"+context.getPropertyOracle().getConfigurationProperty("phpIndexFile").getValues().get(0);

		//PhpRpcRelocatePath anotation = classType.getAnnotation(PhpRpcRelocatePath.class);

		SourceWriter srcAsync = getSourceClientWriter(classType, context, logger);
		if (srcAsync==null){
			return null;
		}
		
		srcAsync.indent();
		srcAsync.println("private JsWebSocket socket;");
		srcAsync.println("private static final String separator = \"_---_\";");
		srcAsync.println("private static final String MODULE_BASE_URL = GWT.getModuleBaseURL();");
		srcAsync.println("private static final String HASH_CLASS_NAME = \""+hashName+"\";");
		srcAsync.println();
		
		srcAsync.println("@Override public void setSocket(JsWebSocket socket) {");
		srcAsync.indentln("this.socket=socket;");
		srcAsync.println("}");
		
		srcAsync.println("@Override public JsWebSocket getsocket() {");
		srcAsync.indentln("return socket;");
		srcAsync.println("}");
		
		srcAsync.println("@Override public String getHash() {");
		srcAsync.indentln("return HASH_CLASS_NAME;");
		srcAsync.println("}");
		
		for (int methodPos = 0; methodPos < methods.length; methodPos++) {
			
			JMethod method = methods[methodPos];

			JParameter[] parameters = method.getParameters();
			srcAsync.println();
			srcAsync.print("@Override ");	
			srcAsync.print("public void "+method.getName()+"(");	
			
			for (int i = 2; i < parameters.length; i++) {
				JParameter parameter = parameters[i];
				srcAsync.print(parameter.getType().getParameterizedQualifiedSourceName()+" "+parameter.getName());	
				if (i < parameters.length-1)
				srcAsync.print(", ");
			}
			srcAsync.println("){");
			srcAsync.indent();
			srcAsync.println("String message_fsdffter = \"\";");	
			srcAsync.println("message_fsdffter += HASH_CLASS_NAME+separator;");
			srcAsync.println("message_fsdffter += \""+methodPos+"\"+separator;");
			srcAsync.println();
			
			
			for (int i = 2; i < parameters.length; i++) {
				JParameter parameter = parameters[i];
				String separator = "";
				if (i<parameters.length-1){
					separator = "+separator";
				}
				String parmName = parameter.getName();
				if (parameter.getType().getClass().getName().endsWith(".JArrayType")){
					if ((parameter.getType().getParameterizedQualifiedSourceName().equals("java.lang.String[]")) ||
							(parameter.getType().getParameterizedQualifiedSourceName().equals("int[]")) ||
							(parameter.getType().getParameterizedQualifiedSourceName().equals("double[]")) ||
							(parameter.getType().getParameterizedQualifiedSourceName().equals("char[]")) ||
							(parameter.getType().getParameterizedQualifiedSourceName().equals("long[]")) ||
							(parameter.getType().getParameterizedQualifiedSourceName().equals("byte[]")) ||
							(parameter.getType().getParameterizedQualifiedSourceName().equals("short[]")) ||
							(parameter.getType().getParameterizedQualifiedSourceName().equals("boolean[]")))
						srcAsync.println("message_fsdffter += WsRpc.toJSONArray("+parmName+").toString()"+separator+";");
						else
						System.err.println("array type : "+parameter.getType().getParameterizedQualifiedSourceName()+" is not accepted");
				}else
				if (parameter.getType().getClass().getName().endsWith(".JPrimitiveType")){
					srcAsync.println("message_fsdffter += new JSONString(String.valueOf("+parmName+")).toString()"+separator+";");
				}else
				if (parameter.getType().getParameterizedQualifiedSourceName().equals("java.lang.String")){
					srcAsync.println("message_fsdffter += new JSONString("+parmName+").toString()"+separator+";");
				}else
				if (parameter.getType().getClass().getName().endsWith(".JRealClassType")){
					srcAsync.println("if ("+parmName+"!=null)");
					srcAsync.indentln("message_fsdffter += "+parmName+".toJSONObject().toString()"+separator+";");
					srcAsync.println("else");
					srcAsync.indentln("message_fsdffter +=  \"null\""+separator+";");
				}else{
					System.err.println("Only real type and jsonObject arguments is accepted");
					throw new UnableToCompleteException();
				}
			}
			srcAsync.println("socket.send(message_fsdffter);");
			srcAsync.outdent();
			srcAsync.println("}");
			
		}
		return srcAsync;
	}

}
